<?php


namespace kyd\kyd_extend\crypt;

ini_set('display_errors','On');

error_reporting(E_ALL);
//未捕获异常的用户定义函数
set_exception_handler(['kyd\kyd_extend\crypt\Rsa','myException']);
//设置用户自定义的错误处理函数

//error_get_last获取不到set_error_handler之后的err,但是set_error_handler之前的错误还是可以获取到。
set_error_handler(['kyd\kyd_extend\crypt\Rsa','myError']);

use Exception;

class Rsa {

    const RSA_CHECKED_PATH = 'path'; //文件路径

    const RSA_CHECKED_STRING = 'string'; //字符串

    //1024bit:分段加密字节数为117，分段解密字节数为128
    //2048bit:分段加密字节数为245，分段解密字节数为256

    protected  $rsa_encrypt_block_size = 245; //加密长度

    protected  $rsa_decrypt_block_size = 256; //解密字符串长度

    protected $public_key; //公钥

    protected $public_key_path; //公钥路径

    protected $private_key; //私钥

    protected $private_key_path; //私钥路径

    protected $checked_path = 'path'; //是否是文件路径 有效值 path/string

    protected $with_base64; //是否返回base64

    static $errorData = []; //错误信息



    /**
     * 初始化配置
     * RsaService constructor.
     * @param $config
     * @throws Exception
     */
    public function __construct($config)
    {

        $this->rsa_encrypt_block_size = isset($config['rsa_encrypt_block_size'])?$config['rsa_encrypt_block_size']:245;
        $this->rsa_decrypt_block_size = isset($config['rsa_decrypt_block_size'])?$config['rsa_decrypt_block_size']:256;

        $this->public_key_path = isset($config['public_key_path'])?$config['public_key_path']:'';
        $this->private_key_path = isset($config['private_key_path'])?$config['private_key_path']:'';

        $this->public_key = isset($config['public_key'])?$config['public_key']:'';
        $this->private_key = isset($config['private_key'])?$config['private_key']:'';

        $this->with_base64 = isset($config['with_base64'])?$config['with_base64']:true;

        $this->checked();
    }


    public function checked(){

        //初始化密钥/公钥
        if (!empty($this->public_key_path) && !empty($this->private_key_path)) {

            if ( $errCode = $this->checkPublicKeyFile() !== true){
                return  $this->errCode($errCode);
            }

            if ($errCode = $this->checkPrivateKeyFile() !== true){
                return  $this->errCode($errCode);
            }

            $this->checked_path = self::RSA_CHECKED_PATH;
        }

        if(!empty($this->public_key)  && !empty($this->private_key)){

            if ($errCode = $this->formatterPublicKey() !== true){
                return  $this->errCode($errCode);
            }

            if ($errCode = $this->formatterPrivateKey() !== true){
                return  $this->errCode($errCode);
            }

            $this->checked_path = self::RSA_CHECKED_STRING;
        }


        //检查私钥是否可用
        if (false === openssl_pkey_get_private($this->private_key)){
            return $this->errCode(RsaCodeEnum::$NOPRIVATEKEY);
        }

        //检查公钥是否可用
        if( false == openssl_pkey_get_public($this->public_key)){
            return $this->errCode(RsaCodeEnum::$NOPUBLICEKEY);
        }


    }


    /**
     * 私钥加密
     * @param $data
     * @param bool $serialize 是为了不管你传的是字符串还是数组，都能转成字符串
     * @return array
     * @throws \Exception
     */
    public function privateEncrypt($data)
    {

        if (is_array($data)){
            $data = json_encode($data,JSON_UNESCAPED_UNICODE);
        }

        $data = base64_encode($data);

        $data = str_split($data, $this->rsa_encrypt_block_size);

        $result = '';

        foreach ($data as $val) {
            openssl_private_encrypt($val,$dataEncrypt, $this->private_key);
            $result .= $dataEncrypt;
        }

        if ($this->with_base64){
            $result =  base64_encode($result);
        }

        return [
            'code'=>RsaCodeEnum::$SUCCESS,
            'message'=>RsaCodeEnum::$OBJECT[RsaCodeEnum::$SUCCESS],
            'data'=>$result
        ];
    }


    /**
     * 私钥解密
     * @param $data
     * @param bool $unserialize
     * @return mixed
     * @throws \Exception
     */

    public function privateDecrypt($data)
    {

        if ($this->with_base64) {
            $data = base64_decode($data);
        }

        $data = str_split($data, $this->rsa_decrypt_block_size);

        $result = '';

        foreach ($data as $val) {
            openssl_private_decrypt($val, $dataDecrypt, $this->private_key);
            $result .= $dataDecrypt;
        }


        if (!$result) {
            return $this->errCode(RsaCodeEnum::$DATAANALYSIS);
        }

        return [
            'code'=>RsaCodeEnum::$SUCCESS,
            'message'=>RsaCodeEnum::$OBJECT[RsaCodeEnum::$SUCCESS],
            'data'=>json_decode($result,true)
        ];

    }


    /**
     * 公钥加密
     * @param $data
     * @param bool $serialize 是为了不管你传的是字符串还是数组，都能转成字符串
     * @return array
     * @throws \Exception
     */

    public function publicEncrypt($data)
    {

        if (is_array($data)){
            $data = json_encode($data,JSON_UNESCAPED_UNICODE);
        }

        $data = base64_encode($data);

        $data = str_split($data, $this->rsa_encrypt_block_size);

        $result = '';

        foreach ($data as $val) {
            openssl_public_encrypt($val,$dataEncrypt, $this->public_key);
            $result .= $dataEncrypt;
        }


        if ($this->with_base64){
            $result = base64_encode($result);
        }

        return [
            'code'=>RsaCodeEnum::$SUCCESS,
            'message'=>RsaCodeEnum::$OBJECT[RsaCodeEnum::$SUCCESS],
            'data'=>$result
        ];
    }


    /**
     * 公钥解密
     * @param $data
     * @param bool $unserialize
     * @return mixed
     * @throws \Exception
     */
    public function publicDecrypt($data)
    {

        if ($this->with_base64) {
            $data = base64_decode($data);
        }

        $data = str_split($data,$this->rsa_decrypt_block_size);

        $result = '';

        foreach ($data as $val) {
            openssl_public_decrypt($val, $dataDecrypt, $this->public_key);
            $result .= $dataDecrypt;
        }

        if (!$result) {
            return $this->errCode(RsaCodeEnum::$DATAANALYSIS);
        }

        return [
            'code'=>RsaCodeEnum::$SUCCESS,
            'message'=>RsaCodeEnum::$OBJECT[RsaCodeEnum::$SUCCESS],
            'data'=>json_decode($result,true)
        ];
    }

    /**
     * 公钥文件是否存在
     * @param $keyPath string 文件路径
     * @return bool
     * @throws
     */

    public function checkPublicKeyFile()
    {
        if (!is_null($this->public_key_path) || file_exists($this->public_key_path)) {
            $this->public_key = file_get_contents($this->public_key_path);
            return true;
        }
        return RsaCodeEnum::$NOPUBLICFILE;
    }


    /**
     * 私钥文件是否存在
     * @param $keyPath string 文件路径
     * @return bool
     * @throws
     */

    public function checkPrivateKeyFile()
    {
        if (!is_null($this->private_key_path) || file_exists($this->private_key_path)) {
            $this->private_key = file_get_contents($this->private_key_path);
            return true;
        }
        return RsaCodeEnum::$NOPRIVATEFILE;
    }



    /**
     * 格式化公钥
     * @param $publicKey string 公钥
     * @return string
     */
    public function formatterPublicKey()
    {

        if (empty($this->public_key)){
            return  RsaCodeEnum::$NOPUBLICEKEY;
        }

        if(strpos($this->public_key,"-----BEGIN PUBLIC KEY-----") === false){
            $this->public_key = str_replace(PHP_EOL, '', $this->public_key);
            $str = chunk_split($this->public_key, 64, PHP_EOL);//在每一个64字符后加一个
            $this->public_key = "-----BEGIN PUBLIC KEY-----".PHP_EOL.$str."-----END PUBLIC KEY-----";
            return true;
        }

    }

    /**
     * 格式化私钥
     * @param $privateKey string 公钥
     * @return string
     */

    public function formatterPrivateKey()
    {

        if (empty($this->private_key)){
            return  RsaCodeEnum::$NOPRIVATEKEY;
        }

        if(strpos($this->private_key,"-----BEGIN PRIVATE KEY-----") === false){
            $this->private_key = str_replace(PHP_EOL, '', $this->private_key);
            $str = chunk_split($this->private_key, 64, PHP_EOL);//在每一个64字符后加一个
            $this->private_key = "-----BEGIN PRIVATE KEY-----".PHP_EOL.$str."-----END PRIVATE KEY-----";
            return true;
        }
    }


    public function errCode($code){
        return ['code'=>$code,'message'=>RsaCodeEnum::$OBJECT[$code],'data'=>[]];
    }



    public static function myException($exception){
        self::$errorData = ['code'=>$exception->getMessage(),'message'=>RsaCodeEnum::$OBJECT[$exception->getMessage()],'data'=>[]];
    }


    /**
     * $error_level
     *  2	E_WARNING	运行时非致命的错误。没有停止执行脚本。
     *  8	E_NOTICE	运行时的通知。脚本发现可能是一个错误，但也可能在正常运行脚本时发生。
     *  256	E_USER_ERROR	用户生成的致命错误。这就如同由程序员使用 PHP 函数 trigger_error() 生成的 E_ERROR。
     *  512	E_USER_WARNING	用户生成的非致命错误。这就如同由程序员使用 PHP 函数 trigger_error() 生成的 E_WARNING。
     *  1024	E_USER_NOTICE	用户生成的通知。这就如同由程序员使用 PHP 函数 trigger_error() 生成的 E_NOTICE。
     *  4096	E_RECOVERABLE_ERROR	可捕获的致命错误。这就如同一个可以由用户定义的句柄捕获的 E_ERROR（见 set_error_handler()）。
     *  8191	E_ALL	所有的错误和警告的级别，除了 E_STRICT（自 PHP 6.0 起，E_STRICT 将作为 E_ALL的一部分）。
     * @param $error_level
     * @param $error_message
     * @param string $error_file
     * @param int $error_line
     * @param array $error_context
     * @return array(3) "err"
     * array(5) {
     *    ["error_level"]=>
     *          int(2)
     *   ["error_message"]=>
     *          string(16) "Division by zero"
     *    ["error_file"]=>
     *  string(21) "D:\doc\test\index.php"
     *          ["error_line"]=>
     *  int(40)
     *  ["error_context"]=>
     *           array(0) {
     *    }
     * }
     */

    public static function myError($error_level, $error_message, $error_file = '', $error_line = 0, $error_context = []){
//        var_dump(compact('error_level', 'error_message', 'error_file', 'error_line', 'error_context'));
        self::$errorData = ['code'=>RsaCodeEnum::$EREOR,'message'=>RsaCodeEnum::$OBJECT[RsaCodeEnum::$EREOR]];
    }
}
